/* -------------------------------------------------------------------------
 *
 * regis.c
 *		Fast regex subset
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/regis.c
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"
#include "knl/knl_variable.h"

#include "tsearch/dicts/regis.h"
#include "tsearch/ts_locale.h"

#define RS_IN_ONEOF 1
#define RS_IN_ONEOF_IN 2
#define RS_IN_NONEOF 3
#define RS_IN_WAIT 4

/*
 * Test whether a regex is of the subset supported here.
 * Keep this in sync with RS_compile!
 */
bool RS_isRegis(const char* str)
{
    int state = RS_IN_WAIT;
    const char* c = str;

    while (*c) {
        if (state == RS_IN_WAIT) {
            if (t_isalpha(c)) {
                /* okay */;
            } else if (t_iseq(c, '[')) {
                state = RS_IN_ONEOF;
            } else {
                return false;
            }
        } else if (state == RS_IN_ONEOF) {
            if (t_iseq(c, '^')) {
                state = RS_IN_NONEOF;
            } else if (t_isalpha(c)) {
                state = RS_IN_ONEOF_IN;
            } else {
                return false;
            }
        } else if (state == RS_IN_ONEOF_IN || state == RS_IN_NONEOF) {
            if (t_isalpha(c)) {
                /* okay */;
            } else if (t_iseq(c, ']')) {
                state = RS_IN_WAIT;
            } else {
                return false;
            }
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("internal error in RS_isRegis: state %d", state)));
        }
        c += pg_mblen(c);
    }

    return (state == RS_IN_WAIT);
}

static RegisNode* newRegisNode(RegisNode* prev, int len)
{
    RegisNode* ptr = NULL;

    ptr = (RegisNode*)palloc0(RNHDRSZ + len + 1);
    if (prev != NULL) {
        prev->next = ptr;
    }
    return ptr;
}

void RS_compile(Regis* r, bool issuffix, const char* str)
{
    int len = strlen(str);
    int state = RS_IN_WAIT;
    const char* c = str;
    RegisNode* ptr = NULL;
    errno_t errorno = EOK;

    errorno = memset_s(r, sizeof(Regis), 0, sizeof(Regis));
    securec_check(errorno, "\0", "\0");
    r->issuffix = (issuffix) ? 1 : 0;

    while (*c) {
        if (state == RS_IN_WAIT) {
            if (t_isalpha(c)) {
                if (ptr != NULL) {
                    ptr = newRegisNode(ptr, len);
                } else {
                    ptr = r->node = newRegisNode(NULL, len);
                }
                COPYCHAR(ptr->data, c);
                ptr->type = RSF_ONEOF;
                ptr->len = pg_mblen(c);
            } else if (t_iseq(c, '[')) {
                if (ptr != NULL) {
                    ptr = newRegisNode(ptr, len);
                } else {
                    ptr = r->node = newRegisNode(NULL, len);
                }
                ptr->type = RSF_ONEOF;
                state = RS_IN_ONEOF;
            } else { /* shouldn't get here */
                ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("invalid regis pattern: \"%s\"", str)));
            }
        } else if (state == RS_IN_ONEOF) {
            if (t_iseq(c, '^')) {
                ptr->type = RSF_NONEOF;
                state = RS_IN_NONEOF;
            } else if (t_isalpha(c)) {
                COPYCHAR(ptr->data, c);
                ptr->len = pg_mblen(c);
                state = RS_IN_ONEOF_IN;
            } else { /* shouldn't get here */
                ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("invalid regis pattern: \"%s\"", str)));
            }
        } else if (state == RS_IN_ONEOF_IN || state == RS_IN_NONEOF) {
            if (t_isalpha(c)) {
                COPYCHAR(ptr->data + ptr->len, c);
                ptr->len += pg_mblen(c);
            } else if (t_iseq(c, ']')) {
                state = RS_IN_WAIT;
            } else { /* shouldn't get here */
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("invalid regis pattern: \"%s\"", str)));
            }
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("internal error in RS_compile: state %d", state)));
        }

        c += pg_mblen(c);
    }

    /* shouldn't get here */
    if (state != RS_IN_WAIT) {
        ereport(
            ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("invalid regis pattern: \"%s\"", str)));
    }
    ptr = r->node;
    while (ptr != NULL) {
        r->nchar++;
        ptr = ptr->next;
    }
}

void RS_free(Regis* r)
{
    RegisNode *ptr = r->node;
    RegisNode *tmp = NULL;

    while (ptr != NULL) {
        tmp = ptr->next;
        pfree_ext(ptr);
        ptr = tmp;
    }

    r->node = NULL;
}

#ifdef USE_WIDE_UPPER_LOWER
static bool mb_strchr(char* str, const char* c)
{
    int clen, plen, i;
    char* ptr = str;
    bool res = false;

    clen = pg_mblen(c);
    while (*ptr && !res) {
        plen = pg_mblen(ptr);
        if (plen == clen) {
            i = plen;
            res = true;
            while (i--) {
                if (*(ptr + i) != *(c + i)) {
                    res = false;
                    break;
                }
            }
        }

        ptr += plen;
    }

    return res;
}
#else
#define mb_strchr(s, c) ((strchr((s), *(c)) == NULL) ? false : true)
#endif

bool RS_execute(Regis* r, char* str)
{
    RegisNode* ptr = r->node;
    char* c = str;
    int len = 0;

    while (*c) {
        len++;
        c += pg_mblen(c);
    }

    if (len < r->nchar) {
        return 0;
    }

    c = str;
    if (r->issuffix) {
        len -= r->nchar;
        while (len-- > 0) {
            c += pg_mblen(c);
        }
    }

    while (ptr != NULL) {
        switch (ptr->type) {
            case RSF_ONEOF:
                if (!mb_strchr((char*)ptr->data, c)) {
                    return false;
                }
                break;
            case RSF_NONEOF:
                if (mb_strchr((char*)ptr->data, c)) {
                    return false;
                }
                break;
            default:
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized regis node type: %u", ptr->type)));
        }
        ptr = ptr->next;
        c += pg_mblen(c);
    }

    return true;
}

